﻿/*!
 * Copyright (c), 上海沃恩信息科技有限公司.
 * All rights reserved.
 * Licensed under BSD (https://www.pittypat.work/bsd.html).
 */

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;

namespace Pittypat.Web
{
    /// <summary>
    /// 提供一组针对 WebSocket 的辅助方法。
    /// </summary>
    static class WebSocketHelper
    {
        /// <summary>
        /// 接收请求数据的最大缓冲区，由配置文件中的 socketDataSize 决定，单位：KB，介于 1KB 和 1024KB 之间。
        /// </summary>
        internal static int MaxBufferSize
        {
            get
            {
                // 配置文件中的值
                var size = Configuration.PittypatConfig.Current.SocketDataSize;
                // 确保值不小于 1KB，不大于 1MB
                return Math.Min(Math.Max(1, size), 1024) * 1024;
            }
        }

        /// <summary>
        /// 最小上传文件的尺寸：1KB。这是一个常量字段。
        /// </summary>
        internal const long MinUploadSize = 1024L;

        /// <summary>
        /// 最大上传文件的尺寸：2GB - 1（2147483647）。这是一个常量字段。
        /// </summary>
        internal const long MaxUploadSize = 2147483647L;

        /// <summary>
        /// 检查 WebSocket 请求的查询字符串中是否包含了 __data=true/yes/1
        /// </summary>
        /// <param name="request">目标请求对象。</param>
        /// <returns>如果请求查询字符串中包含了 __data=true/yes/1，则返回 true，否则返回 false。</returns>
        /// <remarks>
        /// 客户端进行 WebSocket 请求时，需要在 WebSocket 建立连接的请求中包含 __data=true/yes/1 参数来指示客户端解析来将通过 WebSocket 发送请求表单数据。
        /// </remarks>
        static bool HasSocketData(this HttpRequestMessage request)
        {
            var s = "";

            foreach (var item in request.GetQueryNameValuePairs())
            {
                if (item.Key.ToLower() == "__data")
                {
                    s = string.IsNullOrEmpty(item.Value) ? "" : item.Value.ToLower();

                    if (s == "true" || s == "yes" || s == "1")
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// 读取 WebSocket 连接中客户端发送的请求表单字段列表。
        /// </summary>
        /// <param name="request">目标请求对象。</param>
        /// <param name="progress">汇报异步操作的进度。</param>
        /// <param name="buffer">读取数据使用的缓冲区。如果为 null，则在内部创建一个临时缓冲区。缓冲区尺寸为 <see cref="MaxBufferSize"/>。</param>
        /// <returns></returns>
        internal static async Task<NameValueCollection> ReadSocketFormAsync(this HttpRequestMessage request, 
            WebSocketAsyncProgress progress, byte[] buffer = null)
        {
            NameValueCollection form = null;

            // 首先检查请求中是否包含了数据
            if (request.HasSocketData())
            {
                // 接收数据的缓冲区
                var recvBuf = buffer ?? new byte[MaxBufferSize];

                // 异步从连接中读取数据，最多读取的数据长度为缓冲区的尺寸
                int count = await progress.ReceiveAsync(recvBuf);

                if (count > 0)
                {
                    // 将读取的数据解析为表单字段列表
                    form = HttpUtility.ParseQueryString(Encoding.UTF8.GetString(recvBuf, 0, count));
                }
            }

            return form;
        }

        /// <summary>
        /// WebSocket 子协议标头名称。这是一个常量字段，值为 "Sec-WebSocket-Protocol"。
        /// </summary>
        const string SecWebSocketProtocolHeader = "Sec-WebSocket-Protocol";
        /// <summary>
        /// 上传文件的子协议。这是一个常量字段，值为 "upload.pittypat"。
        /// </summary>
        internal const string UploadProtocol = "upload.pittypat";
        /// <summary>
        /// 允许的子协议列表。这是一个只读静态字段。目前仅仅包含 "upload.pittypat"。
        /// </summary>
        static readonly string[] SupportedSubProtocols = { UploadProtocol };

        /// <summary>
        /// 获取指定的 WebSocket 升级请求中包含的子协议的列表中的第一个元素。
        /// </summary>
        /// <param name="request">目标请求。</param>
        /// <returns>允许的第一个子协议。如果没有受支持的子协议，则返回空字符串。</returns>
        internal static string GetWebSocketSubProtocol(this HttpRequestMessage request)
        {
            IEnumerable<string> protocols = null;
            if (request.Headers.TryGetValues(SecWebSocketProtocolHeader, out protocols))
            {
                foreach (var item in protocols)
                {
                    if (SupportedSubProtocols.Contains(item, StringComparer.OrdinalIgnoreCase))
                    {
                        return item;
                    }
                }
            }

            return "";
        }

        /// <summary>
        /// 将允许的子协议添加到响应中。
        /// </summary>
        /// <param name="response">目标响应。</param>
        /// <param name="subprotocol">允许的子协议。</param>
        /// <returns>响应对象本身。</returns>
        internal static HttpResponseMessage SetAcceptedSubProtocol(this HttpResponseMessage response, string subprotocol)
        {
            if (!string.IsNullOrEmpty(subprotocol) && SupportedSubProtocols.Contains(subprotocol, StringComparer.OrdinalIgnoreCase))
            {
                response.Headers.Add(SecWebSocketProtocolHeader, subprotocol);
            }

            return response;
        }
    }
}
